Earth Engine workshop exercises

Can we simplify this script?

Exercise:

Instead of returning a FeatureCollection of sampledPoints around each buffered point, modify the samplePoints function so that it:

  1. Takes the mean of all the samplePoints within a bufferzone using the same “reduceColumns” method I showed you earlier.

  2. Creates a feature with a null geometry and the bufferzone mean assigned to the buffered point ID from which the points were sampled.

  3. Uses this new function and a mapping operation to create the meanPopDensFC variable without needing to compute sampledPoints, meanPopDens, or meanPopDensList.

time:30 minutes

Modified “samplePoints” function

// Function to sample random points within each buffered area
var samplePoints = function (feature) {
  var geometry = feature.geometry();
  var randomPoints = ee.FeatureCollection.randomPoints(geometry, 10);
  var sampledPoints = popDensYearBuffered.sampleRegions({
    collection: randomPoints,
    scale: 1000,
    tileScale: 16
  })
  // Add the buffered area ID to each sampled point
  sampledPoints = sampledPoints.map(function(point) {
    return point.set('BufferedAreaID', feature.get('ID'));
  });

  // Calculate the mean 'population_density' for each group
  var meanPopDens = sampledPoints.reduceColumns({
    reducer: ee.Reducer.mean(),
    selectors: ['population_density']
  });
  return ee.Feature(null, {
    'BufferedAreaID': feature.get('ID'),
    'mean': meanPopDens.get('mean')
  });
};

// Sample 10 random points within each buffered area and calculate the mean population density
var meanPopDensFC = bufferedPoints.map(samplePoints);

Can we get rid of needing to assign ID’s?

If we take the mean inside of samplePoints we could also do the filtering inside the function and return geometries without needing to access a bufferedPoint’s ID.

Exercise:

Modify the “samplePoints” function so that it: 1. doesn’t need to assign an “ID” value to “BufferedAreaID”. 2. Filters out mean population density values less than 100 people per sq. km. 3. returns a “highDensityPoint” candidate directly.

time: 30 minutes

Further modified “samplePoints” function

// Function to sample random points within each buffered area
var samplePoints = function (feature) {
  var geometry = feature.geometry();
  var randomPoints = ee.FeatureCollection.randomPoints(geometry, 10);
  var sampledPoints = popDensYearBuffered.sampleRegions({
    collection: randomPoints,
    scale: 1000,
    tileScale: 16
  })

  // Calculate the mean 'population_density' for each group
  var meanPopDens = sampledPoints.reduceColumns({
    reducer: ee.Reducer.mean(),
    selectors: ['population_density']
  });

  // Filter out mean population density values 
  // less than 100 people per sq. km.
  var mean = meanPopDens.get('mean');
  if (mean < 100) {
    return null;
  }

  // return a "highDensityPoint" candidate
  return ee.Feature(geometry, {
    'mean': mean
  });
};

// Map highDensityPoints to get candidate points
var highDensityPoints = bufferedPoints.map(samplePoints)
                                      .filter(ee.Filter.neq(null));

Stripping out ID assignment from rest of script

Exercise:

Remove ID assignment from 1st part of the script by:

  1. Rename the “bufferAndSetID” function to just “bufferPoint”.

  2. Modify this new “bufferPoint” function so that it takes in a point feature instead of an index and then just buffers this point instead of both buffering and doing index assignment.

  3. Map this new function over the intersectionPoints featureCollection to get a new “bufferedPoints” featureCollection that doesn’t have id’s.

time: 20 minutes

New “bufferPoint” function and “bufferedPoints” assignment

// Function to buffer a point
var bufferPoint = function(feature) {
  // Buffer the point
  var bufferedPoint = feature.buffer(10000);
  // Return the buffered point
  return bufferedPoint;
};

// Create a buffer at every intersection point
var bufferedPoints = intersectionPoints.map(bufferPoint);

Seperating out functions

Another way to simplify might be to create a new module to store functions we have created in this script.

Exercise:

  • Create an additional file called “Helpers”.
  • Then use the “Docs” tab in the lefthand pane to research how to include this file’s exported functions into your port finding script.

time: 10 minutes

How to include your helper module

To include this module we will add the following line to the beginning of our port finding script:


// Import the lineToPoint function
var hlp = require('users/dylanblee/REUworkshop:Helpers');

Put the “lineToPoint” function into your helper module

Exercise:

  • Research how to export a function from a javascript script.
  • Migrate the “lineToPoint” function from port finder script to helper module.
  • Call lineToPoint from helper module in your modified script instead of from the main script.

time: 20 minutes

“lineToPoint” in the helper module

Here is what we added to the helper module:

exports.lineToPoint = function(feature) {
  // Get the geometry
  var geom = feature.geometry();
  // Get the centroid of the geometry
  var centroid = geom.centroid();
  // Create a new point feature
  var pointFeature = ee.Feature(centroid, feature.toDictionary());
  return pointFeature;
};

and here is how we call this function:

// Apply the lineToPoint function to each element
// in intersectionPoints
var intersectionPoints = selectedLines.map(hlp.lineToPoint);

Modularizing “bufferAndSetID”

We would also like to modularize other functions we have created. For example, “bufferPoint”.

Exercise:

put “bufferPoint” into your helper module and call it from your script.

time: 10 minutes

Modularizing the “samplePoints” function

The “samplePoints” function is doing alot of work for us. Can we decompose this function and move some of its pieces out to our module?

Exercise:

  1. Create an exported “calculateMeanDensity” function inside of your “Helper” module that takes a geometry and a number of points to sample.
  2. Call this function from inside of a modified “samplePoints” function.

time: 20 minutes

new “calculateMeanDensity” function

Added to “Helpers”:

// Function to calculate the mean 'population_density' for each group of sampled points
exports.calculateMeanDensity = function (popDensYearBuffered, randomPoints) {
  var sampledPoints = popDensYearBuffered.sampleRegions({
    collection: randomPoints,
    scale: 1000,
    tileScale: 16
  });

  var meanPopDens = sampledPoints.reduceColumns({
    reducer: ee.Reducer.mean(),
    selectors: ['population_density']
  });
  
  return meanPopDens.get('mean');
};

Calling “calculateMeanDensity” from new “samplePoints”

// Function to sample random points within each buffered area
var samplePoints = function (feature) {
  var geometry = feature.geometry();
  var randomPoints = ee.FeatureCollection.randomPoints(geometry, 10);
  var mean = hlp.calculateMeanDensity(popDensYearBuffered, randomPoints);
  
  // Filter out mean population density values less than 100 people per sq. km.
  if (mean < 100) {
    return null;
  }

  // return a "highDensityPoint" candidate
  return ee.Feature(geometry, {
    'mean': mean
  });
};

Modularizing the “samplePoints” function

Exercise:

Further simplify the “samplePoints” function by taking the logic that filters the population density values to return highDensityPoints into a function called “filterHighDensityPoints”

time: 10 minutes

Exported “filterHighDensityPoints”

// Function to filter out mean population density 
// values less than 100 people per sq. km
// and return a "highDensityPoint" candidate
var filterHighDensityPoints = function (geometry, meanDensity) {
  if (meanDensity < 100) {
    return null;
  }
  
  return ee.Feature(geometry, {
    'mean': meanDensity
  });
};

Modified “samplePoints”

// Function to sample random points within each buffered area
var samplePoints = function (feature) {
  var geometry = feature.geometry();
  var randomPoints = ee.FeatureCollection.randomPoints(geometry, 10);
  var mean = hlp.calculateMeanDensity(popDensYearBuffered, randomPoints);
  // return filtered mean population density values less than 100 people per sq. km.
  return hlp.filterHighDensityPoints(geometry, mean);
};

Profiling code using the EE online editor

Exercise:

Write a short description of what each column in the profiler tab is describing.

Profiling code using the EE online editor

Exercise:

  1. Profile our refactored script. List some things that could be slowing our script down.
  2. Try to make a change to the script. Rerun the profiler again and see what changes.

Profiling code using the EE online editor

We can do a more manual style of profiling to hone in on how changing the way we are getting intersection points speeds up or slows down the function.

// Get a timestamp before calling the function
var start = Date.now();

// Call the function

// Get a timestamp after calling the function
var end = Date.now();

// Calculate the execution time
var executionTime = end - start;

print('Execution time: ', executionTime + ' ms');